/*
 * GeoVISTA Center (Penn State, Dept. of Geography)
 * Copyright (c), 2003-2011,  Jin Chen and Alan M. MacEachren, GeoVISTA Center, Penn State University
 * All Rights Researved.
 *
 * This library is developed and distributed for free usage,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * You can redistribute and/or modify it under the terms of the
 * Simplified BSD License
 *
 *
 *
 *
 *
 *
  * This library is developed and distributed for free usage,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * You can redistribute and/or modify it under the terms of the
 * Simplified BSD License
 *
 *
 *
 *
 *
 *
 *
 *
 * @Original Author: jin Chen
 * @date: Dec 3, 2003$
 * @version: 1.0
 */
package edu.psu.geovista.app.dataloader.v1.session;


import edu.psu.geovista.app.dataloader.v1.config.model.Project;
import edu.psu.geovista.app.dataloader.v1.xml.Dom4jPathsXMLFs;
import edu.psu.geovista.app.dataloader.v1.xml.Dom4jProjectsXMLFs;
import edu.psu.geovista.app.dataloader.v1.xml.PathsXMLFileSystem;
import edu.psu.geovista.app.dataloader.v1.xml.ProjectsXMLFileSystem;
import edu.psu.geovista.common.utils.TimeUtils;
import edu.psu.geovista.common.xml.dom4j.XMLParserDom4j;
import org.apache.commons.beanutils.BeanUtils;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;


public class ProjectBuilder {

    // 
    public static final boolean DEBUG=false;

    public static final String Project_LastOne="Last Project";
    public static final String Project_New="New Project";

    public static final String PATHS = "paths.xml";  // 
    public static final String PROJECTS="projects.xml";
    public static final  String cfgHome="/.VitPub/config/";
    public static final  String WORKSPACE="/.VitPub/workspace/";// 
    public static final int MaxNumOfSession=20;//maximum number of session in the document
    String pathInfoFileName="pathInfo.xml";
    public static final String projfoFileName="projInfo.xml";
    public static final String Element_ID="ID";
    public static final String Tag_SessionRoot="SESSIONS";
    public static final String Tag_Path="Path";
    public static final String Tag_Session="Session";
    public static final String Attr_Name="Name";
    public static final String Attr_Value="Value";
    public static final String Attr_Time="Time";


    File rootDir;
    Document document;

    File pathInfo;  // 
    File projInfo; // 

    PathsXMLFileSystem pathsXml;  //xml file system contain paths info
    ProjectsXMLFileSystem projsXml;//xml file system contain projects info


    public ProjectBuilder() throws IOException {
        File rootDir = getUserHomeConfigDir();//by default
        this.setRootDir(rootDir);
        //loadProjectInfo();
        //setup projsXml


    }



    public void loadProjectInfo() throws IOException {
        String workspace = getWorkspaceRoot();
        File wsdir=new File(workspace);
        
        // 
        // 
        projsXml=new Dom4jProjectsXMLFs();
        File projectsFile = getProjectsFile();
        projsXml.setFile(projectsFile);
        projsXml.load();

        // pathsXml's file is project-specific, need set it when load the project
        pathsXml = new Dom4jPathsXMLFs();
    }


    /*******************************************************************************************************
     *                services
     *******************************************************************************************************/
    /************************************
     *                update project
     ************************************/
    /**
     * It only update project in memory(specifically, in XmlfileSystem's project attribute) , not in physical file. save() will make physical file changed.
     * @param proj
     */
    public void  updateProject(Project proj) throws IOException {
        String id = proj.getID();
        Project project = projsXml.getProject(id);
        if (project==null){//it is a new project
            projsXml.addProject(proj);

           /* put it in Dom4jPahtsXMLFs
           File projRoot = getProjectRootDir(id);
            if(!projRoot.exists() ){
                projRoot.mkdirs();
                File pathsFile = getPathsFile(id);
                pathsFile.createNewFile() ;
                pathsXml.setFile(pathsFile);
            }*/
        }
        else{//existing one, update it.
            try {
                //projsXml.deleteProject(project);
                //projsXml.addProject(proj);
                BeanUtils.copyProperties(project,proj);
            } catch (IllegalAccessException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            } catch (InvocationTargetException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
        }

        this.pathsXml.setProject(proj);

    }
    /**************************************************
     *               get a project
     ***************************************************/
    /**
     *
     * @param id  project id
     * @return
     */
    public Project getProject(String id){
        Project project=null;
        File projfile = getPathsFile(id);
        System.out.println(this.getClass().getName() +" get project file "+projfile.getAbsolutePath());
        if(projfile.exists() ){//means project exist
            System.out.println(this.getClass().getName() +" found "+projfile);
            Dom4jPathsXMLFs  pathsXml =(Dom4jPathsXMLFs) this.getPathsXml();
            pathsXml.setFile(projfile);
            System.out.println(" create a pathsXml, ready to load xml");
            pathsXml.load();
            System.out.println(this.getClass().getName() +" load xml");
            project = pathsXml.getProject();
        }
        else{//mean the project not exist

        }
        if(project!=null)
            project.setID(id);//to avoid bug,
        return project;
       // return  this.getPathsXml().getProjectByID(id);
    }


    /*public Project getProjectByIDIngoreCase(String id){


        return this.getPathsXml().getProjectByIDIngoreCase(id);
    }*/
    /**
     *
     * @return   a list of Id of the project. The Ids are string-sort
     */
     public List<String> getSortedProjectIDs(){
       /* List ids=new ArrayList();
        String wsroot = this.getWorkspaceRoot();


        File wsdir=new File(wsroot);
        File[] files = wsdir.listFiles();
        for (int i=0;i<files.length ;i++){
            File file = files[i];
            if (file.isDirectory()){
                String name = file.getName();
                ids.add(name);
            }
        }
        return ids;*/
        return projsXml.getSortedProjectIDs() ;
     }
    /**
     * Don't make it public, it should not accessed by ProjectBuilder
     * @return
     */
    protected PathsXMLFileSystem getPathsXml() {
        return pathsXml;
    }
    /**************************************************
     *                clean to reclaim memory
     ***************************************************/
    public void cleanResource() {
        this.pathsXml=null;
    }

    /*******************************************************************************************************
     *                support
     *******************************************************************************************************/
   /* public ProjectType getCurrPj() {
        return currPj;
    }

    public void setCurrPj(ProjectType currPj) {
        this.currPj = currPj;
    }*/
    public void save() throws IOException {
        //1.current proj
       // this.currPj.setTime(TimeUtil.getCurrentTimeString() );
        //keepLimitedProject();   //todo this may not work
        this.projsXml.save();
        this.pathsXml.save() ;
    }
    private void keepLimitedProject(){
        List projlist=pathsXml.getSortProjectList() ;
        if(projlist!=null) {
        while(projlist.size() >MaxNumOfSession){
            int last=projlist.size() -1;
            projlist.remove(last);
        }

        }
    }

    /**************************************************
     *                get file & dir
     ***************************************************/
    public static File getWorkspaceDir() {
        File root=new File(getWorkspaceRoot());
        if(!root.exists() ){
            root.mkdirs();
        }
        return root;
    }
     /**
     *
     * @return  workspace root dir in string format
     */
    public static String getWorkspaceRoot(){
        String userHomePath= getUserHome() ;
        String workspace=userHomePath+WORKSPACE ;//userHomePath+Upgrader.WORKSPACE;
        return workspace;
    }
    public static String getUserHome(){
        String userHome=System.getProperty("user.home");
        String userHomePath=userHome.replace('\\','/');
        return   userHomePath;
    }
    /**
     *
     * @return   
     * @throws IOException
     */
    public static File getProjectsFile() throws IOException{

                File wsdir=getWorkspaceDir();
                File projsFile=new File(wsdir,PROJECTS);
                if(!projsFile.exists() ){
                         projsFile.createNewFile() ;
                }
                return projsFile;

    }


    private static String getProjectRoot(String projId) {
        String wsroot =getWorkspaceRoot();
        String projroot=wsroot+projId;
        return projroot;
    }


    /**
     *
     * @param id
     * @return   the xml file named as <PATHS>. It contains path info
     */
    public  static File getPathsFile(String id) {
        String projroot = getProjectRoot(id);
        String path=projroot+"/"+PATHS;
        File projfile=new File(path);
        return projfile;
    }


    /*******************************************************************************************************
     *               deprecated
     *******************************************************************************************************/
    /**
     * Load the pathInfo.xml document. If the file not exist, create one.
     * The method guarantee to return a non-null document
     * @return
     */
    private Document loadDocument(){
        Document doc=null;
        pathInfo=new File(this.getRootDir() ,pathInfoFileName);

        if(pathInfo.exists() ){
            try {
                doc= XMLParserDom4j.loadDocument(pathInfo);


            } catch (Exception e) {
                // 
                //todo show the exception?
                if(DEBUG)   {
                    System.out.println(" Unable to load document. The exception is only for warning:");
                    e.printStackTrace();
                }
                doc=DocumentHelper.createDocument();
            }
        }
        else{

            doc= DocumentHelper.createDocument();
        }
        return doc;
    }
    protected  File getRootDir(){
        // File rootDir = getUserHomeConfigDir();
        return rootDir;
    }

    public void setRootDir(File rootDir) {
        this.rootDir = rootDir;
    }

    /**
     * return path of .pcp/config that under <user_home>
     * @return
     */
    public static File getUserHomeConfigDir() {
        String userHomePath=getUserHome() ;
        String myHome =userHomePath+cfgHome;
        File rootDir =new File(myHome);
        if(rootDir.exists() && rootDir.isDirectory() ){
         //return;
     }
     else{
         boolean created=rootDir.mkdirs() ;
         if(created){
             System.out.println("create dir:"+rootDir);

         }
         else{
             System.out.println("Fail to create dir:"+rootDir);
         }
     }
        return rootDir;
    }

    /**
     * if the session element not exist  in document, add it. It only update document in memory, not in file.
     * @param ses
     */
    public void updateSession(edu.psu.geovista.app.dataloader.v1.session.Project ses) {
        /*Document document = DocumentHelper.createDocument();
        Element root = document.addElement( "root" );*/
        Element root=getRoot();
        Element sesElement=XMLParserDom4j.getChildElement(root,Tag_Session,Element_ID,ses.getId() );
         if(sesElement==null)
             sesElement=root.addElement( Tag_Session );
        //set session element
          sesElement.addAttribute( Element_ID, ses.getId() );
                sesElement.addAttribute( Attr_Time, TimeUtils.convertDateToString(ses.getTime()) );
          //set path element
            Properties paths=ses.getPaths() ;
            Enumeration names=paths.keys() ;
            while (names.hasMoreElements()) {
                String name = (String) names.nextElement();
                String value=paths.getProperty(name);
                Element pathElement=XMLParserDom4j.getChildElement(sesElement,Tag_Path,Attr_Name,name);
                if(pathElement==null)
                    pathElement=sesElement.addElement(Tag_Path);

                 pathElement.addAttribute(Attr_Name,name)
                            .addAttribute(Attr_Value,value);
            }

     }
     public String  getProjectDate(String projectName) {

         Element root=getRoot();
         Element session=XMLParserDom4j.getChildElement(root,Tag_Session,Element_ID,projectName);
         String date=session.attributeValue(Attr_Time);
         return date;
     }
     public Properties getProjectPathInfo(String projectName){
         Properties props=null;
         Element root=getRoot();
         Element session=XMLParserDom4j.getChildElement(root,Tag_Session,Element_ID,projectName);
         if(session!=null){
             props=new Properties();
             ArrayList elements=  XMLParserDom4j.getChildElements(session,Tag_Path);

             for (Iterator iterator = elements.iterator(); iterator.hasNext();) {
                 Element element = (Element) iterator.next();
                 String name=element.attributeValue(Attr_Name);
                 String value=element.attributeValue(Attr_Value);
                 props.setProperty(name,value);

             }
         }
         return props;
     }
    /**
     * Sort is done in descending way that last session is the 1st element
     * @return
     */
     public String[] getSortProjectNames(){
        String[] sns=new String[0];
        try {
            Element root=getRoot();
            ArrayList elements=XMLParserDom4j.getChildElements(root,Tag_Session);
            edu.psu.geovista.app.dataloader.v1.session.Project[] sessions=new edu.psu.geovista.app.dataloader.v1.session.Project[elements.size()];
            sns=new String[elements.size() ];//session name
            int j=0;
            DateFormat fmt=new SimpleDateFormat();
            for (Iterator iterator = elements.iterator(); iterator.hasNext();) {
                Element session = (Element) iterator.next();
                String name=session.attributeValue(Element_ID);

                    Date time=null;
                    try {
                        time=fmt.parse(session.attributeValue(Attr_Time));
                    } catch (ParseException e) {
                        e.printStackTrace();  //To change body of catch statement use Options | File Templates.
                    }
                    sessions[j++]=new edu.psu.geovista.app.dataloader.v1.session.Project(name,time);

                //sns[i++]=name;

            }

            Arrays.sort(sessions);
            int k=0;
            for (int i=sessions.length-1;i>=0 ;i--){
                sns[k++]=sessions[i].getId() ;
            }
        } catch (Exception e) {
            e.printStackTrace();
            //todo
        }
        return sns ;
     }
     private Element getRoot() {
         Element root=getDocument().getRootElement() ;
         if (root==null)
             root=getDocument().addElement( Tag_SessionRoot );
         return root;
     }
    /**
     * Only keep <MaxNumberOfSession> projects, if more exist, remove the project with earliest changed date
     */
    private void checkDocument(){

          Element root=getDocument().getRootElement() ;
          ArrayList sesElements=XMLParserDom4j.getChildElements(root,Tag_Session);
          int num=sesElements.size() ;
           if(MaxNumOfSession<num){ //document has more element than allowed
          edu.psu.geovista.app.dataloader.v1.session.Project[] sessions=new edu.psu.geovista.app.dataloader.v1.session.Project[num];
          DateFormat format=new SimpleDateFormat ();
          for (int i=0;i<sessions.length ;i++){
              Element se=(Element) sesElements.get(i);

              String name=se.attributeValue(Element_ID);
              String date=se.attributeValue(Attr_Time);
              Date d=null;
              try {
                  d= format.parse(date) ; //new Date(date);

              } catch (ParseException e) {
                  e.printStackTrace();  //To change body of catch statement use Options | File Templates.
                  d=TimeUtils.getCurrentTimeDate() ;
              }
              sessions[i]=new edu.psu.geovista.app.dataloader.v1.session.Project(name,d);
          }
          Arrays.sort(sessions);
          edu.psu.geovista.app.dataloader.v1.session.Project[] finalSes=new edu.psu.geovista.app.dataloader.v1.session.Project[MaxNumOfSession];
          HashSet finalNames=new HashSet();
          System.arraycopy(sessions,sessions.length -finalSes.length,finalSes,0,finalSes.length );
          for (int i=0;i<finalSes.length ;i++){
              finalNames.add(finalSes[i].getId() );
          }

          for (Iterator iterator = sesElements.iterator(); iterator.hasNext();) {
              Element element = (Element) iterator.next();
              String name=element.attributeValue(Element_ID);
              if(!finalNames.contains(name) ){
                  root.remove(element);
              }
          }

           }
    }




    private  Document getDocument() {
        if(document==null){
            document=this.loadDocument() ;
        }
        return document;
    }
}
